from __future__ import annotations

from dataclasses import MISSING, asdict, fields, is_dataclass
from typing import Any, Callable
import json

from . import channel_hydraulics as ch
from . import hydraulic_structures_book as hs
from . import hydropower as hp
from . import reservoir_routing as rr
from . import reservoir_storage as rs
from . import river_evolution as re
from . import river_levee as rl
from . import waterworks_irrigation as wi
from .core import HydrologyError
from .engineering_labels import LABELS, UNITS


def _f(params: dict[str, Any], key: str, default: float) -> float:
    value = params.get(key, default)
    if value in (None, ""):
        value = default
    try:
        return float(value)
    except (TypeError, ValueError) as exc:
        raise HydrologyError(f"{key} 必须是数字") from exc


def _s(params: dict[str, Any], key: str, default: str) -> str:
    value = params.get(key, default)
    return str(value if value not in (None, "") else default)


def _fmt(value: Any) -> str:
    if value is None:
        return "-"
    if isinstance(value, float):
        return f"{value:.10g}"
    return str(value)


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


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


def _book(
    title: str,
    input_rows: list[dict[str, str]],
    result_rows: list[dict[str, str]],
    formula: str = "",
    source: str = "",
) -> str:
    lines = [title, "", "一、公式来源", source or "见程序登记表与对应规范条文"]
    lines.extend(["", "二、参数代入值", "| 项目 | 数值 | 单位 | 说明 |", "|---|---:|---|---|"])
    lines.extend(f"| {r['name']} | {r['value']} | {r.get('unit', '')} | {r.get('note', '')} |" for r in input_rows)
    lines.extend(["", "三、计算过程与中间量"])
    if formula:
        lines.extend(["采用公式：", formula])
    lines.append("以下结果按公式的求解顺序列出；其中包含断面、荷载、流量、尺寸等中间量及最终校核结论。计算过程保留全精度，界面显示值不参与后续回算。")
    lines.extend(["", "四、逐项结果与校核", "| 项目 | 数值 | 单位 | 说明 |", "|---|---:|---|---|"])
    lines.extend(f"| {r['name']} | {r['value']} | {r.get('unit', '')} | {r.get('note', '')} |" for r in result_rows)
    return "\n".join(lines)


def _payload(
    title: str,
    result: Any,
    input_rows: list[dict[str, str]],
    result_rows: list[dict[str, str]],
    *,
    source: str,
    tables: list[dict[str, Any]] | None = None,
    series: list[dict[str, Any]] | None = None,
    aliases: dict[str, Any] | None = None,
) -> dict[str, Any]:
    formula = str(getattr(result, "formula", "") or "")
    # Every adapter retains parameter, formula, and result tables. The desktop
    # implementation exposed these blocks, while early Web pages only showed
    # metric cards for many programs.
    detail_tables = [
        _table(
            "参数代入与公式核对表",
            [
                *input_rows,
                _row("公式来源", source),
                *([_row("采用公式", formula)] if formula else []),
            ],
        ),
        _table("计算过程与成果汇总", result_rows),
    ]
    payload = {
        "metrics": result_rows,
        "input_rows": input_rows,
        "result_rows": result_rows,
        "tables": [*detail_tables, *(tables or [])],
        "source": source,
        "formula": formula,
        "raw": asdict(result),
        "calculation_book_text": _book(title, input_rows, result_rows, formula, source),
    }
    if series:
        payload["series"] = series
    if aliases:
        payload.update(aliases)
    return payload


def _parse_curve(text: str, default: tuple[tuple[float, float], ...], label: str) -> tuple[tuple[float, float], ...]:
    value = (text or "").strip()
    if not value:
        return default
    rows: list[tuple[float, float]] = []
    for index, chunk in enumerate(value.replace("\n", ";").split(";"), start=1):
        chunk = chunk.strip()
        if not chunk:
            continue
        parts = [part.strip() for part in chunk.split(",")]
        if len(parts) != 2:
            raise HydrologyError(f"{label}第{index}段格式应为 x,y")
        try:
            rows.append((float(parts[0]), float(parts[1])))
        except ValueError as exc:
            raise HydrologyError(f"{label}第{index}段必须是数字") from exc
    return tuple(rows or default)


def _parse_layers(text: str) -> list[rs.ReservoirCapacityLayer]:
    default = "5,1500,1000;5,2300,1500"
    rows: list[rs.ReservoirCapacityLayer] = []
    for index, chunk in enumerate((text or default).replace("\n", ";").split(";"), start=1):
        chunk = chunk.strip()
        if not chunk:
            continue
        parts = [part.strip() for part in chunk.split(",")]
        if len(parts) != 3:
            raise HydrologyError(f"分层库容第{index}段格式应为 高度,上面积,下面积")
        rows.append(rs.ReservoirCapacityLayer(float(parts[0]), float(parts[1]), float(parts[2])))
    return rows


def _simple_result_adapter(
    title: str,
    params: dict[str, Any],
    data: Any,
    calculator: Callable[[Any], Any],
    input_rows: list[dict[str, str]],
    names: dict[str, tuple[str, str, str]],
    *,
    source: str,
    aliases: dict[str, Any] | None = None,
) -> dict[str, Any]:
    result = calculator(data)
    raw = asdict(result)
    result_rows = [_row(label, raw.get(key), unit, note) for key, (label, unit, note) in names.items()]
    return _payload(title, result, input_rows, result_rows, source=source, aliases=aliases)


# @hook UI3-3 2-01 20260804
def calculate_irr_01(params: dict[str, Any]) -> dict[str, Any]:
    area = _f(params, "irrigation_area_mu", 1000)
    quota = _f(params, "quota_m3_per_mu", 60)
    duration = _f(params, "duration_days", 7)
    rows = [
        _row("灌溉面积", area, "亩"),
        _row("灌水定额", quota, "m³/亩"),
        _row("延续时间", duration, "d"),
    ]
    return _simple_result_adapter(
        "引水灌溉灌水模数",
        params,
        wi.IrrigationModulusInput(area, quota, duration),
        wi.calculate_irrigation_modulus,
        rows,
        {"irrigation_modulus": ("灌水模数", "m³/(s·万亩)", ""), "net_flow": ("作物净流量", "m³/s", "")},
        source="《水利大算盘》第一章第1节",
    )


# @hook UI3-3 2-04 20260804
def calculate_irr_04(params: dict[str, Any]) -> dict[str, Any]:
    data = wi.PumpDesignRateFlowInput(
        _f(params, "crop_ratio_percent", 60),
        _f(params, "quota_m3_per_mu", 75),
        _f(params, "duration_days", 7),
        _f(params, "daily_pumping_hours", 20),
        _f(params, "total_area_10k_mu", 0.5),
        _f(params, "irrigation_efficiency", 0.75),
    )
    rows = [
        _row("作物种植比例", data.crop_ratio_percent, "%"),
        _row("灌水定额", data.quota_m3_per_mu, "m³/亩"),
        _row("延续时间", data.duration_days, "d"),
        _row("日提水时间", data.daily_pumping_hours, "h"),
        _row("总灌溉面积", data.total_area_10k_mu, "万亩"),
        _row("灌溉水利用系数", data.irrigation_efficiency, "-"),
    ]
    return _simple_result_adapter(
        "提水灌溉设计流量",
        params,
        data,
        wi.calculate_pump_flow_by_design_rate,
        rows,
        {"irrigation_rate": ("净灌水率", "m³/(s·万亩)", ""), "design_flow": ("提水设计流量", "m³/s", "")},
        source="《水利大算盘》第一章第4节",
        aliases={"peak_flow": None},
    )


# @hook UI3-3 2-06 20260804
def calculate_irr_06(params: dict[str, Any]) -> dict[str, Any]:
    data = wi.PumpHeadInput(_f(params, "outlet_water_level", 1256), _f(params, "suction_water_level", 1248), _f(params, "total_head_loss", 2.5))
    rows = [_row("出水池水位", data.outlet_water_level, "m"), _row("吸水池水位", data.suction_water_level, "m"), _row("总水头损失", data.total_head_loss, "m")]
    return _simple_result_adapter(
        "水泵设计扬程",
        params,
        data,
        wi.calculate_pump_design_head,
        rows,
        {"static_head": ("净扬程", "m", ""), "design_head": ("设计扬程", "m", "")},
        source="《水利大算盘》第一章第6节",
    )


# @hook UI3-3 3-02 20260804
def calculate_chn_02(params: dict[str, Any]) -> dict[str, Any]:
    data = ch.OpenChannelInput(
        _f(params, "bottom_width", 3.0),
        _f(params, "water_depth", 1.2),
        _f(params, "side_slope", 1.5),
        _f(params, "roughness", 0.025),
        _f(params, "bed_slope", 0.0002),
        _f(params, "enlarge_coefficient", 1.15),
    )
    rows = [_row("底宽", data.bottom_width, "m"), _row("水深", data.water_depth, "m"), _row("边坡系数", data.side_slope), _row("糙率", data.roughness), _row("比降", data.bed_slope)]
    return _simple_result_adapter(
        "明渠水力",
        params,
        data,
        ch.calculate_open_channel,
        rows,
        {
            "area": ("过水面积", "m²", ""),
            "wetted_perimeter": ("湿周", "m", ""),
            "hydraulic_radius": ("水力半径", "m", ""),
            "velocity": ("流速", "m/s", ""),
            "flow": ("流量", "m³/s", ""),
            "freeboard": ("渠岸超高", "m", ""),
        },
        source="《水利大算盘》第二章第2节",
        aliases={"peak_flow": None},
    )


# @hook UI3-3 3-09 20260804
def calculate_riv_01(params: dict[str, Any]) -> dict[str, Any]:
    data = rl.RiverDischargeEstimateInput(_f(params, "average_depth", 2.5), _f(params, "water_width", 80), _f(params, "water_surface_slope", 0.001), _f(params, "roughness", 0.035))
    rows = [_row("平均水深", data.average_depth, "m"), _row("水面宽", data.water_width, "m"), _row("水面比降", data.water_surface_slope), _row("糙率", data.roughness)]
    result = rl.calculate_river_discharge_estimate(data)
    result_rows = [_row("河道流速", result.velocity, "m/s"), _row("河道流量", result.flow, "m³/s")]
    return _payload("河道流量估算", result, rows, result_rows, source="《水利大算盘》第三章第1节", aliases={"peak_flow": result.flow})


# @hook UI3-3 3-10 20260804
def calculate_riv_02(params: dict[str, Any]) -> dict[str, Any]:
    data = rl.TrainingLineWidthInput(_f(params, "flow", 1200), _f(params, "roughness", 0.035), _f(params, "training_depth", 3.0), _f(params, "water_surface_slope", 0.001))
    rows = [_row("设计流量", data.flow, "m³/s"), _row("糙率", data.roughness), _row("治导水深", data.training_depth, "m"), _row("水面比降", data.water_surface_slope)]
    result = rl.calculate_training_line_width(data)
    result_rows = [_row("治导线宽度", result.width, "m")]
    return _payload("河道治导线宽度", result, rows, result_rows, source="《水利大算盘》第三章第2节")


# @hook UI3-3 3-11 20260804
def calculate_riv_03(params: dict[str, Any]) -> dict[str, Any]:
    data = rl.ManningLeveeCrestInput(
        _f(params, "flow", 1200),
        _f(params, "bottom_width", 60),
        _f(params, "side_slope", 2),
        _f(params, "roughness", 0.035),
        _f(params, "bed_slope", 0.001),
        _f(params, "wind_speed", 18),
        _f(params, "fetch", 1000),
        _f(params, "safety_freeboard", 0.8),
    )
    rows = [_row("设计流量", data.flow, "m³/s"), _row("底宽", data.bottom_width, "m"), _row("边坡", data.side_slope), _row("糙率", data.roughness), _row("比降", data.bed_slope), _row("风速", data.wind_speed, "m/s"), _row("吹程", data.fetch, "m"), _row("安全加高", data.safety_freeboard, "m")]
    result = rl.calculate_manning_levee_crest(data)
    result_rows = [_row("计算水深", result.water_depth, "m"), _row("波浪爬高/风壅组合", result.wave_runup, "m"), _row("堤顶超高", result.crest_freeboard, "m"), _row("堤身高度", result.levee_height, "m")]
    return _payload("堤顶超高和堤顶高程", result, rows, result_rows, source="《水利大算盘》第三章第4节", aliases={"water_level": result.levee_height})


# @hook UI3-3 5-01 20260804
def calculate_rsr_01(params: dict[str, Any]) -> dict[str, Any]:
    inflow = _parse_curve(_s(params, "inflow_hydrograph", ""), ((0, 100), (1, 300), (2, 800), (3, 500), (4, 200)), "入库过程线")
    storage = _parse_curve(_s(params, "elevation_storage_curve", ""), ((1200, 1000000), (1205, 2000000), (1210, 4000000)), "水位库容曲线")
    discharge = _parse_curve(_s(params, "elevation_discharge_curve", ""), ((1200, 20), (1205, 200), (1210, 800)), "水位泄量曲线")
    data = rr.ReservoirRoutingInput(_s(params, "reservoir_name", "调洪水库"), inflow, storage, discharge, _f(params, "initial_elevation_m", 1200))
    result = rr.route_reservoir_flood(data)
    input_rows = [_row("水库名称", data.reservoir_name), _row("初始水位", data.initial_elevation_m, "m"), _row("入库过程线点数", len(inflow), "个"), _row("水位库容点数", len(storage), "个"), _row("水位泄量点数", len(discharge), "个")]
    result_rows = [_row("入库洪峰", result.peak_inflow_m3s, "m³/s"), _row("最大下泄", result.peak_outflow_m3s, "m³/s"), _row("峰现延迟", result.peak_delay_h, "h"), _row("削峰率", result.peak_reduction_percent, "%"), _row("最高调洪水位", result.max_elevation_m, "m"), _row("相对水量平衡误差", result.mass_balance_relative_percent, "%")]
    step_rows = [_row(f"t={step.time_h:.2f}h", step.outflow_m3s, "m³/s", f"Z={step.elevation_m:.3f}m, 入流={step.inflow_m3s:.3f}m³/s") for step in result.steps]
    series = [
        {"title": "入库洪水过程线", "unit": "m³/s", "xKey": "time_h", "yKey": "inflow_m3s", "points": [{"time_h": s.time_h, "inflow_m3s": s.inflow_m3s} for s in result.steps]},
        {"title": "出库流量过程线", "unit": "m³/s", "xKey": "time_h", "yKey": "outflow_m3s", "points": [{"time_h": s.time_h, "outflow_m3s": s.outflow_m3s} for s in result.steps]},
        {"title": "调洪水位过程线", "unit": "m", "xKey": "time_h", "yKey": "elevation_m", "points": [{"time_h": s.time_h, "elevation_m": s.elevation_m} for s in result.steps]},
    ]
    return _payload("水库调洪演算", result, input_rows, result_rows, source="软件内置水库调洪演算", tables=[_table("逐时段调洪成果", step_rows)], series=series, aliases={"peak_flow": result.peak_outflow_m3s, "water_level": result.max_elevation_m})


# @hook UI3-3 5-04 20260804
def calculate_rsr_04(params: dict[str, Any]) -> dict[str, Any]:
    data = rs.EarthDamFreeboardInput(_f(params, "water_depth_m", 20), _f(params, "wind_speed_m_s", 20), _f(params, "fetch_m", 1000), _f(params, "wind_water_axis_angle_deg", 0), _f(params, "wave_period_s", 3), _f(params, "upstream_slope", 2.5), _f(params, "slope_permeability_coefficient", 0.85), _f(params, "empirical_coefficient", 1.0), _f(params, "safety_margin_m", 0.5))
    rows = [_row("水深", data.water_depth_m, "m"), _row("风速", data.wind_speed_m_s, "m/s"), _row("吹程", data.fetch_m, "m"), _row("风向夹角", data.wind_water_axis_angle_deg, "°"), _row("波浪周期", data.wave_period_s, "s"), _row("上游边坡", data.upstream_slope), _row("安全加高", data.safety_margin_m, "m")]
    result = rs.calculate_earth_dam_freeboard(data)
    result_rows = [_row("波高", result.wave_height_m, "m"), _row("波长", result.wave_length_m, "m"), _row("波浪爬高", result.wave_runup_m, "m"), _row("风壅水面高度", result.wind_setup_m, "m"), _row("坝顶超高", result.freeboard_m, "m"), _row("坝顶高程", result.crest_elevation_m, "m")]
    return _payload("土石坝坝顶超高", result, rows, result_rows, source="《水利大算盘》第五章第4节", aliases={"water_level": result.crest_elevation_m})


# @hook UI3-3 5-08 20260804
def calculate_rsr_08(params: dict[str, Any]) -> dict[str, Any]:
    layers = _parse_layers(_s(params, "layers", ""))
    data = rs.ReservoirCapacityInput(layers, _f(params, "cone_height_m", 2), _f(params, "cone_top_area_m2", 1000))
    result = rs.calculate_reservoir_capacity(data)
    rows = [_row("分层数量", len(layers), "层"), _row("底部锥体高度", data.cone_height_m, "m"), _row("锥体上部面积", data.cone_top_area_m2, "m²")]
    result_rows = [_row("底部锥体库容", result.cone_volume_m3, "m³"), _row("总库容", result.total_capacity_m3, "m³")]
    layer_rows = [_row(f"第{idx + 1}层", value, "m³") for idx, value in enumerate(result.layer_volumes_m3)]
    return _payload("水库库容计算", result, rows, result_rows, source="《水利大算盘》第五章第8节", tables=[_table("分层库容", layer_rows)])


# @hook UI3-3 6-01 20260804
def calculate_hyd_01(params: dict[str, Any]) -> dict[str, Any]:
    data = hp.HydropowerOutputInput(_f(params, "design_flow", 20), _f(params, "gross_head", 60), _f(params, "head_loss", 3), _f(params, "turbine_efficiency_percent", 88), _f(params, "output_coefficient", 9.81))
    rows = [_row("设计流量", data.design_flow, "m³/s"), _row("毛水头", data.gross_head, "m"), _row("水头损失", data.head_loss, "m"), _row("水轮机效率", data.turbine_efficiency_percent, "%"), _row("出力系数", data.output_coefficient)]
    result = hp.calculate_hydropower_output(data)
    result_rows = [_row("工作水头", result.working_head, "m"), _row("水电站出力", result.station_output_kw, "kW"), _row("水轮机输入功率", result.turbine_input_kw, "kW"), _row("水轮机输出功率", result.turbine_output_kw, "kW")]
    return _payload("水电站水能出力", result, rows, result_rows, source="《水利大算盘》第四章第1节")


# @hook UI3-3 6-03 20260804
def calculate_hyd_03(params: dict[str, Any]) -> dict[str, Any]:
    data = hp.PressurePipeHydropowerInput(_f(params, "design_flow", 20), _f(params, "max_design_flow", 25), _f(params, "design_velocity", 3), _f(params, "design_head", 60), _f(params, "max_head", 70), _f(params, "coefficient_k", 1.0), _f(params, "weld_coefficient", 0.9), _f(params, "allowable_stress_kg_cm2", 1200))
    rows = [_row("设计流量", data.design_flow, "m³/s"), _row("最大设计流量", data.max_design_flow, "m³/s"), _row("设计流速", data.design_velocity, "m/s"), _row("设计水头", data.design_head, "m"), _row("最大水头", data.max_head, "m"), _row("允许应力", data.allowable_stress_kg_cm2, "kg/cm²")]
    result = hp.calculate_pressure_pipe_hydropower(data)
    result_rows = [_row("贝耶公式管径", result.diameter_by_beyer_m, "m"), _row("流速控制管径", result.diameter_by_velocity_m, "m"), _row("采用管径", result.selected_diameter_m, "m"), _row("强度计算壁厚", result.strength_thickness_mm, "mm"), _row("设计壁厚", result.design_thickness_mm, "mm")]
    return _payload("压力管道直径和壁厚", result, rows, result_rows, source="《水利大算盘》第四章第3节")


# @hook UI3-3 6-06 20260804
def calculate_hyd_06(params: dict[str, Any]) -> dict[str, Any]:
    data = hp.WaterHammerInput(_f(params, "pipe_diameter", 2.5), _f(params, "wall_thickness", 0.02), _f(params, "pipe_material_elastic_mpa", 206000), _f(params, "water_bulk_modulus_mpa", 2100), _f(params, "pipe_length", 500), _f(params, "closing_time", 5), _f(params, "initial_velocity", 3), _f(params, "final_velocity", 0.5), _f(params, "steady_head", 60))
    rows = [_row("管径", data.pipe_diameter, "m"), _row("壁厚", data.wall_thickness, "m"), _row("管长", data.pipe_length, "m"), _row("关闭时间", data.closing_time, "s"), _row("初流速", data.initial_velocity, "m/s"), _row("末流速", data.final_velocity, "m/s"), _row("稳定水头", data.steady_head, "m")]
    result = hp.calculate_water_hammer(data)
    result_rows = [_row("水击波速", result.wave_velocity, "m/s"), _row("反射时间", result.reflection_time, "s"), _row("水锤类型", result.hammer_type), _row("间接水锤水头", result.indirect_head, "m"), _row("最大水头", result.maximum_head, "m"), _row("允许水头", result.allowable_head, "m")]
    return _payload("压力管道水锤压力", result, rows, result_rows, source="《水利大算盘》第四章第6节", aliases={"water_level": result.maximum_head})


# @hook UI3-3 7-01 20260804
def calculate_hst_01(params: dict[str, Any]) -> dict[str, Any]:
    data = hs.SluiceHydraulicInput(_f(params, "flow_m3s", 100), _f(params, "upstream_head_m", 3), _f(params, "downstream_head_m", 1), _f(params, "gate_design_head_m", 4), _f(params, "freeboard_m", 0.5), _f(params, "discharge_coefficient", 0.4), _f(params, "contraction_coefficient", 1.0), _f(params, "submergence_coefficient", 1.0), _f(params, "opening_count", 1.0))
    rows = [_row("设计流量", data.flow_m3s, "m³/s"), _row("上游水头", data.upstream_head_m, "m"), _row("下游水头", data.downstream_head_m, "m"), _row("闸门设计水头", data.gate_design_head_m, "m"), _row("安全超高", data.freeboard_m, "m")]
    result = hs.calculate_sluice_hydraulic(data)
    result_rows = [_row("总净宽", result.total_clear_width_m, "m"), _row("闸室长度", result.chamber_length_m, "m"), _row("门槽宽度", result.groove_width_cm, "cm"), _row("门槽深度", result.groove_depth_cm, "cm")]
    return _payload("水闸水力计算", result, rows, result_rows, source="《水利大算盘》第六章第1节")


# @hook UI3-3 7-02 20260804
def calculate_hst_02(params: dict[str, Any]) -> dict[str, Any]:
    width = _f(params, "weir_width_m", 0.0)
    data = hs.SideWeirInput(_s(params, "mode", "直角分水"), _f(params, "flow_m3s", 30), width or None, _f(params, "discharge_coefficient", 0.38), _f(params, "approach_velocity_m_s", 1.2), _f(params, "approach_depth_m", 2.0), _f(params, "diversion_angle_deg", 90), _f(params, "upstream_weir_head_m", 1.2), _f(params, "downstream_weir_head_m", 1.0))
    rows = [_row("堰型", data.mode), _row("流量", data.flow_m3s, "m³/s"), _row("已知堰宽", data.weir_width_m or 0, "m", "填0自动反算"), _row("流量系数", data.discharge_coefficient), _row("上游堰顶水头", data.upstream_weir_head_m, "m"), _row("下游堰顶水头", data.downstream_weir_head_m, "m")]
    result = hs.calculate_side_weir(data)
    result_rows = [_row("平均堰上水头", result.mean_weir_head_m, "m"), _row("侧堰宽度", result.side_weir_width_m, "m"), _row("复核泄流能力", result.side_weir_flow_m3s, "m³/s")]
    return _payload("溢流堰水力计算", result, rows, result_rows, source="《水利大算盘》第六章第2节", aliases={"peak_flow": result.side_weir_flow_m3s})


# @hook UI3-3 7-03 20260804
def calculate_hst_03(params: dict[str, Any]) -> dict[str, Any]:
    data = hs.SpillwayChuteInput(_f(params, "flow_m3s", 100), _f(params, "bottom_width_m", 8), _f(params, "bed_slope", 0.02), _f(params, "roughness", 0.015), _f(params, "side_slope", 1.5), _f(params, "aeration_coefficient", 1.0))
    rows = [_row("泄量", data.flow_m3s, "m³/s"), _row("底宽", data.bottom_width_m, "m"), _row("底坡", data.bed_slope), _row("糙率", data.roughness), _row("边坡", data.side_slope)]
    result = hs.calculate_spillway_chute(data)
    result_rows = [_row("正常水深", result.normal_depth_m, "m"), _row("流速", result.velocity_m_s, "m/s"), _row("掺气水深", result.aerated_depth_m, "m"), _row("安全超高", result.freeboard_m, "m"), _row("边墙高度", result.sidewall_height_m, "m")]
    return _payload("正槽式溢洪道泄槽", result, rows, result_rows, source="《水利大算盘》第六章第3节", aliases={"peak_flow": data.flow_m3s})


# @hook UI3-3 7-07 20260804
def calculate_hst_07(params: dict[str, Any]) -> dict[str, Any]:
    data = hs.AqueductInput(_f(params, "flow_m3s", 10), _f(params, "trough_length_m", 200), _f(params, "bed_slope", 0.001), _f(params, "roughness", 0.015), _f(params, "upstream_channel_width_m", 3.8), _f(params, "upstream_channel_depth_m", 1.845), _f(params, "upstream_velocity_m_s", 1.426), _f(params, "upstream_bed_elevation_m", 4500), _f(params, "trough_bottom_width_m", 3.2))
    rows = [_row("渡槽流量", data.flow_m3s, "m³/s"), _row("槽身长度", data.trough_length_m, "m"), _row("底坡", data.bed_slope), _row("糙率", data.roughness), _row("槽身底宽", data.trough_bottom_width_m, "m")]
    result = hs.calculate_aqueduct(data)
    result_rows = [_row("槽身水深", result.trough_depth_m, "m"), _row("槽身流速", result.trough_velocity_m_s, "m/s"), _row("总水头损失", result.total_head_loss_m, "m"), _row("槽首底高程", result.trough_start_bed_m, "m"), _row("槽末底高程", result.trough_end_bed_m, "m"), _row("安全超高", result.freeboard_cm, "cm")]
    return _payload("引水渡槽水力", result, rows, result_rows, source="《水利大算盘》第六章第7节")


# @hook UI3-3 7-08 20260804
def calculate_hst_08(params: dict[str, Any]) -> dict[str, Any]:
    data = hs.InvertedSiphonInput(_f(params, "flow_m3s", 8), _f(params, "design_velocity_m_s", 2.0), _f(params, "pipe_length_m", 150), _f(params, "headloss_coefficient_sum", 1.2), _f(params, "friction_factor", 0.02), _f(params, "upstream_bed_m", 4500), _f(params, "upstream_depth_m", 2.0), _f(params, "downstream_depth_m", 1.8), _f(params, "allowable_stress_kg_cm2", 1200), _f(params, "design_head_m", 20))
    rows = [_row("流量", data.flow_m3s, "m³/s"), _row("设计流速", data.design_velocity_m_s, "m/s"), _row("管长", data.pipe_length_m, "m"), _row("局部损失系数和", data.headloss_coefficient_sum), _row("沿程阻力系数", data.friction_factor), _row("设计水头", data.design_head_m, "m")]
    result = hs.calculate_inverted_siphon(data)
    result_rows = [_row("管径", result.pipe_diameter_m, "m"), _row("总水头损失", result.total_headloss_m, "m"), _row("管壁厚度", result.wall_thickness_mm, "mm"), _row("最小壁厚", result.minimum_wall_thickness_mm, "mm"), _row("出口渠底高程", result.downstream_bed_m, "m"), _row("弯管半径", result.bend_radius_m, "m")]
    return _payload("倒虹吸管水力", result, rows, result_rows, source="《水利大算盘》第六章第8节", aliases={"water_level": result.downstream_bed_m})


_FIELD_LABELS = {
    "area": "流域面积",
    "catchment_area_km2": "汇水面积",
    "design_flow": "设计流量",
    "design_flow_m3_s": "设计流量",
    "design_flow_m3s": "设计流量",
    "flow": "流量",
    "flow_m3s": "流量",
    "bottom_width": "底宽",
    "bottom_width_m": "底宽",
    "water_depth": "水深",
    "roughness": "糙率",
    "bed_slope": "底坡",
    "side_slope": "边坡系数",
    "velocity": "流速",
    "diameter": "管径",
    "pipe_diameter": "管径",
    "length": "长度",
    "pipe_length": "管长",
    "water_head_m": "水头",
    "gross_head": "毛水头",
    "working_head": "工作水头",
    "design_head": "设计水头",
    "design_head_m": "设计扬程",
    "wind_speed_m_s": "风速",
    "fetch_m": "吹程",
    "safety_freeboard_m": "安全超高",
}

_FIELD_UNITS = {
    "area": "km²",
    "catchment_area_km2": "km²",
    "flow": "m³/s",
    "flow_m3s": "m³/s",
    "design_flow": "m³/s",
    "design_flow_m3_s": "m³/s",
    "design_flow_m3s": "m³/s",
    "peak_flow_m3s": "m³/s",
    "water_depth": "m",
    "bottom_width": "m",
    "bottom_width_m": "m",
    "diameter": "m",
    "pipe_diameter": "m",
    "length": "m",
    "pipe_length": "m",
    "water_head_m": "m",
    "gross_head": "m",
    "working_head": "m",
    "design_head": "m",
    "design_head_m": "m",
    "wind_speed_m_s": "m/s",
    "fetch_m": "m",
    "safety_freeboard_m": "m",
    "rainfall_intensity_mm_h": "mm/h",
    "design_24h_rainfall_mm": "mm",
    "net_rainfall_mm": "mm",
}

_DEFAULT_VALUES = {
    "basin_name": "示例流域",
    "region_id": 1,
    "region": 1,
    "area": 47.02,
    "catchment_area_km2": 47.02,
    "river_length": 13.8,
    "slope": 0.095724,
    "cv": 0.45,
    "flow": 120.0,
    "flow_m3s": 120.0,
    "design_flow": 120.0,
    "design_flow_m3_s": 120.0,
    "design_flow_m3s": 120.0,
    "roughness": 0.035,
    "bed_slope": 0.001,
    "water_depth": 2.0,
    "bottom_width": 6.0,
    "bottom_width_m": 6.0,
    "side_slope": 1.5,
    "velocity": 2.0,
    "diameter": 1.2,
    "pipe_diameter": 1.2,
    "length": 500.0,
    "pipe_length": 500.0,
    "rainfall_decay_exponent": 0.67,
    "peak_flow_coefficient": 0.278,
    "design_24h_rainfall_mm": 95.0,
    "net_rainfall_mm": 55.0,
    "rainfall_intensity_mm_h": 45.0,
    "runoff_modulus": 3.5,
    "effective_capacity_m3": 3000000.0,
    "seconds_per_month": 2592000.0,
    "bearing_type": "滑动支承",
    "mode": "无压箱涵",
    "block_density_t_m3": 2.65,
    "water_density_t_m3": 1.0,
    "slope_coefficient": 1.5,
    "slope_change_coefficient": 1.0,
    "wind_speed_m_s": 20.0,
    "fetch_m": 500.0,
    "wave_period_s": 3.0,
    "basin_width_m": 8.0,
    "upstream_depth_m": 1.2,
    "approach_velocity_m_s": 4.0,
    "drop_height_m": 1.0,
    "downstream_depth_m": 2.5,
    "pressure_on_gate_kN": 120.0,
    "water_head_m": 4.0,
    "soil_friction_angle_deg": 30.0,
    "soil_unit_weight": 18.0,
    "wall_height": 3.0,
    "groundwater_depth": 0.0,
    "water_unit_weight": 9.81,
    "channel_inner_water_depth": 1.5,
    "structure_factor": 1.2,
    "importance_factor": 1.0,
    "design_condition_factor": 1.0,
    "concrete_fc": 9.6,
    "wall_thickness_mm": 400.0,
    "cover_thickness_mm": 30.0,
    "steel_diameter_mm": 12.0,
    "steel_fy": 300.0,
    "steel_spacing_mm": 200.0,
    "steel_layers": 1,
    "total_depth": 3.0,
    "main_depth": 2.0,
    "main_bottom_width": 4.0,
    "main_side_slope": 1.5,
    "floodplain_side_slope": 1.5,
    "left_floodplain_width": 4.0,
    "right_floodplain_width": 4.0,
    "main_roughness": 0.025,
    "floodplain_roughness": 0.035,
    "water_width": 2.0,
    "wall_thickness_m": 0.02,
    "inner_diameter_m": 2.0,
    "steel_yield_strength_mpa": 235.0,
    "steel_elastic_modulus_mpa": 206000.0,
    "max_load_kw": 1000.0,
    "rated_voltage_kv": 10.0,
    "economic_current_density_a_mm2": 1.1,
    "power_factor": 0.85,
    "allowable_temperature_c": 80,
    "generator_total_active_power_kw": 5000.0,
    "bus_minimum_load_kw": 200.0,
    "rated_power_factor": 0.85,
    "standard_capacity_sum_kva": 6300.0,
    "installed_load_kw": 12000.0,
    "demand_factor": 0.7,
    "max_load_utilization_hours": 4500.0,
    "base_load_kw": 7600.0,
    "annual_growth_rate_percent": 4.0,
    "forecast_years": 10.0,
    "grid_loss_rate_percent": 6.0,
    "plant_use_rate_percent": 3.0,
    "downstream_channel_width_m": 4.0,
    "upstream_head_m": 2.0,
    "submergence_coefficient": 0.98,
    "height_width_ratio": 1.5,
    "arch_angle_deg": 120.0,
    "pressure_section_area_m2": None,
    "pressure_head_correction_m": 0.0,
    "culvert_depth_m": 1.0,
    "correction_coefficient": 0.748,
    "weir_width_m": None,
    "discharge_coefficient": 0.38,
    "approach_depth_m": 1.5,
    "diversion_angle_deg": 45.0,
    "upstream_weir_head_m": 1.2,
    "downstream_weir_head_m": 1.0,
    "normal_weir_coefficient": 0.428,
}


def _label(key: str) -> str:
    return LABELS.get(key, _FIELD_LABELS.get(key, key.replace("_", " ")))


def _unit(key: str) -> str:
    if key in UNITS:
        return UNITS[key]
    for suffix, unit in [('_m2s','m²/s'), ('_m_s','m/s'), ('_m2','m²'), ('_m3','m³'), ('_kN','kN'), ('_mpa','MPa'), ('_kva','kVA'), ('_t','t')]:
        if key.endswith(suffix): return unit
    if key in _FIELD_UNITS:
        return _FIELD_UNITS[key]
    if key.endswith("_m3s"):
        return "m³/s"
    if key.endswith("_m"):
        return "m"
    if key.endswith("_mm"):
        return "mm"
    if key.endswith("_kw"):
        return "kW"
    if key.endswith("_percent"):
        return "%"
    if key.endswith("_deg"):
        return "°"
    return ""


def _default_for(key: str, default: Any = None) -> Any:
    if default is not MISSING and default is not None:
        return default
    if key in _DEFAULT_VALUES:
        return _DEFAULT_VALUES[key]
    if "efficiency" in key or "coefficient" in key:
        return 0.85
    if "slope" in key:
        return 0.001
    if "level" in key or "elevation" in key:
        return 1200.0
    if "head" in key:
        return 10.0
    if "depth" in key:
        return 2.0
    if "width" in key:
        return 5.0
    if "area" in key:
        return 100.0
    if "flow" in key:
        return 20.0
    if "length" in key:
        return 100.0
    if "time" in key or "years" in key:
        return 10.0
    if "count" in key:
        return 1
    return 1.0


def _float_list(value: Any, default: str) -> list[float]:
    text = str(value if value not in (None, "") else default)
    return [float(part.strip()) for part in text.replace(";", ",").split(",") if part.strip()]


def _coerce_value(params: dict[str, Any], key: str, default: Any, target_type: Any) -> Any:
    if 'float | None' in str(target_type) and default is None and params.get(key) in (None, ''):
        return None
    raw = params.get(key, _default_for(key, default))
    if raw in (None, ""):
        raw = _default_for(key, default)
    text_type = str(target_type)
    if target_type is bool or text_type == "bool":
        if isinstance(raw, bool):
            return raw
        return str(raw).strip().lower() in {"1", "true", "yes", "y", "是"}
    if target_type is int or text_type == "int":
        numeric = float(raw)
        if not numeric.is_integer():
            raise HydrologyError(f'{_label(key)}必须为整数，不能自动截断小数。')
        return int(numeric)
    if target_type is str or text_type == "str":
        return str(raw)
    if "list[float]" in text_type:
        return _float_list(raw, str(_default_for(key, default)))
    if "float | None" in text_type and raw in (None, ""):
        return None
    return float(raw)


def _dataclass_input(input_cls: type, params: dict[str, Any], extras: dict[str, Any] | None = None) -> Any:
    extras = extras or {}
    kwargs: dict[str, Any] = {}
    for field in fields(input_cls):
        if field.name in extras:
            kwargs[field.name] = extras[field.name]
            continue
        default = field.default if field.default is not MISSING else _default_for(field.name)
        if str(field.type).startswith("tuple["):
            kwargs[field.name] = default if default is not MISSING else ()
            continue
        kwargs[field.name] = _coerce_value(params, field.name, default, field.type)
    return input_cls(**kwargs)


# Form contracts deliberately mirror the concrete calculator input classes.  The
# original Web UI used several param_1-style placeholder fields here; those
# values were not names accepted by the calculator and therefore fell back to
# defaults.  Keep this mapping next to the adapters so UI fields cannot drift
# away from the formula inputs again.
_FORM_INPUT_CLASSES: dict[str, tuple[type, ...]] = {
    "2-02": (wi.ContinuousDiversionInput,),
    "2-03": (wi.CanalHeadwaterInput,),
    "2-05": (wi.PressurePipeInput,),
    "2-07": (wi.PumpInstallationInput,),
    "2-08": (wi.PumpPowerInput, wi.PumpEfficiencyInput, wi.PumpSpecificSpeedInput),
    "3-01": (ch.IntakeAngleInput,),
    "3-03": (ch.NonScourVelocityInput,),
    "3-04": (ch.NonSiltingVelocityInput,),
    "3-05": (ch.CriticalDepthInput,),
    "3-06": (ch.BestHydraulicSectionInput,),
    "3-07": (ch.PracticalEconomicSectionInput,),
    "3-08": (ch.LinedSeepageInput,),
    "3-18": (ch.ChannelSidewallStabilityInput,),
    "3-19": (ch.CompoundChannelInput,),
    "3-20": (ch.UChannelInput,),
    "5-02": (rs.ReservoirSedimentationInput,),
    "5-05": (rs.EarthDamProfileInput,),
    "5-06": (rs.DamSlopeProtectionInput,),
    "5-07": (rs.EarthDamProfileInput,),
    "5-09": (rs.PlaneGateForceInput,),
    "6-02": (hp.InstalledCapacityInput,),
    "6-04": (hp.PipeSubmergenceInput,),
    "6-05": (hp.VentPipeInput,),
    "6-07": (hp.PipeHeadLossInput,),
    "6-08": (hp.ExternalPressureInput,),
    "6-09": (hp.TransmissionConductorInput,),
    "6-10": (hp.TransformerCapacityInput,),
    "6-11": (hp.LoadForecastInput,),
    "7-05": (hs.SteepSlopeInput,),
    "7-06": (hs.VerticalDropInput,),
    "7-09": (hs.BridgeHydraulicInput,),
    "7-10": (hs.TunnelHydraulicInput,),
    "7-11": (hs.CulvertHydraulicInput,),
    "7-12": (hs.SideWeirInput,),
}

_FORM_ADDITIONAL_FIELDS: dict[str, list[dict[str, Any]]] = {
    "3-21": [
        {
            "key": "groups_json",
            "label": "钢筋分组明细",
            "default": "",
            "unit": "JSON",
            "type": "textarea",
            "required": True,
            "help": "按 JSON 数组录入每组钢筋。每组必须有 name、diameter_mm、unit_weight_kg_per_m、structure_length_m、protection_total_thickness_m、spacing_m、straight_length_m、bends_count；可选 segment_count、layer_count。示例格式见本页计算说明；正式项目应按配筋图逐组录入。",
        },
        {
            "key": "concrete_volume_m3",
            "label": "对应混凝土体积",
            "default": 1.0,
            "unit": "m³",
            "type": "number",
            "required": True,
            "help": "与本次统计范围对应的混凝土实体体积，用于计算含钢量。",
        },
        {
            "key": "loss_rate_percent",
            "label": "钢筋损耗率",
            "default": 3.0,
            "unit": "%",
            "type": "number",
            "required": True,
            "help": "按采购、下料和施工组织方案确定；仅用于由净重换算采购毛重。",
        },
    ],
    "3-22": [
        {
            "key": "bed_slope",
            "label": "渠道设计底坡",
            "default": 0.001,
            "unit": "m/m",
            "type": "number",
            "required": True,
            "help": "按渠道纵断面设计控制坡度录入；相邻桩号渠底高程按桩距乘本坡度递推。",
        },
        {
            "key": "design_depth",
            "label": "设计水深",
            "default": 2.0,
            "unit": "m",
            "type": "number",
            "required": True,
            "help": "用于计算设计水位：设计水位 = 渠底高程 + 设计水深。",
        },
        {
            "key": "minimum_depth",
            "label": "最小水深",
            "default": 1.2,
            "unit": "m",
            "type": "number",
            "required": True,
            "help": "用于绘制最低运行水位线。",
        },
        {
            "key": "enlarged_depth",
            "label": "加大水深",
            "default": 2.2,
            "unit": "m",
            "type": "number",
            "required": True,
            "help": "用于计算加大水位和渠顶高程。",
        },
        {
            "key": "freeboard",
            "label": "渠顶超高",
            "default": 0.5,
            "unit": "m",
            "type": "number",
            "required": True,
            "help": "渠顶高程 = 加大水位 + 渠顶超高。",
        },
        {
            "key": "first_bed_elevation",
            "label": "起点渠底高程",
            "default": 1200.0,
            "unit": "m",
            "type": "number",
            "required": True,
            "help": "第一个桩号点的设计渠底高程；其后点由桩距和底坡递推。",
        },
        {
            "key": "points_json",
            "label": "纵断面桩号与地面高程",
            "default": "",
            "unit": "JSON",
            "type": "textarea",
            "required": True,
            "help": "按桩号顺序录入 JSON 数组，每项为 {\"station_no\":1,\"ground_elevation\":1202.3,\"distance_from_previous\":0}；首点桩距必须为 0，后续桩距为与前点的水平距离（m）。",
        },
    ],
    "5-03": [
        {
            "key": "inflows_m3s",
            "label": "逐月来水流量",
            "default": "",
            "unit": "m³/s",
            "type": "textarea",
            "required": True,
            "help": "按 1 至 12 月顺序输入 12 个值，使用逗号或换行分隔；不允许缺月或用示例序列替代项目资料。",
        },
        {
            "key": "demands_m3s",
            "label": "逐月需水流量",
            "default": "",
            "unit": "m³/s",
            "type": "textarea",
            "required": True,
            "help": "按 1 至 12 月顺序输入 12 个值，使用逗号或换行分隔；不允许缺月或用示例序列替代项目资料。",
        },
        {
            "key": "effective_capacity_m3",
            "label": "有效库容",
            "default": 3000000.0,
            "unit": "m³",
            "type": "number",
            "required": True,
            "help": "采用死水位至正常蓄水位间的有效库容；应与水位库容关系资料一致。",
        },
    ],
}


_FORM_FIELD_OVERRIDES: dict[tuple[str, str], dict[str, Any]] = {
    ("6-09", "allowable_temperature_c"): {
        "type": "select",
        "options": [{"value": "70", "label": "70 ℃"}, {"value": "80", "label": "80 ℃"}],
        "help": "按导线允许工作温度选择；程序按内置 LGJ 导线载流量表复核推荐导线。",
    },
    ("7-10", "mode"): {
        "type": "select",
        "default": "无压城门洞形",
        "options": [
            {"value": "无压城门洞形", "label": "无压城门洞形"},
            {"value": "无压圆形", "label": "无压圆形"},
            {"value": "有压矩形", "label": "有压矩形"},
        ],
        "help": "按隧洞过流工况选择。无压城门洞形按堰流关系反算宽高；无压圆形按曼宁关系求管径；有压矩形按压力流关系复核。",
    },
    ("7-11", "mode"): {
        "type": "select",
        "default": "无压箱涵",
        "options": [{"value": "无压箱涵", "label": "无压箱涵"}, {"value": "有压箱涵", "label": "有压箱涵"}],
        "help": "按涵洞运行水力状态选择；有压工况须核定涵内水深、涵长、底坡和修正系数。",
    },
    ("7-12", "mode"): {
        "type": "select",
        "default": "锐角分水",
        "options": [{"value": "锐角分水", "label": "锐角分水"}, {"value": "直角分水", "label": "直角分水"}],
        "help": "按分水布置选择。堰宽留空时按设计流量反算；填入堰宽时按该宽度复核泄量。",
    },
    ("7-12", "weir_width_m"): {
        "default": "",
        "required": False,
        "help": "可留空反算侧堰宽度；若已知实际堰宽，请填入后校核相应泄流量。",
    },
}


def _form_field_schema(dataclass_field: Any) -> dict[str, Any]:
    key = dataclass_field.name
    text_type = str(dataclass_field.type)
    default = dataclass_field.default
    required = default is MISSING
    if required:
        default = _default_for(key)
    elif default is None:
        default = ""

    field_type = "number"
    options: list[dict[str, str]] | None = None
    if key == "canal_segments":
        field_type = "textarea"
        default = "[]"
    elif key == "bearing_type":
        field_type = "select"
        options = [
            {"value": "滑动支承", "label": "滑动支承"},
            {"value": "滚动支承", "label": "滚动支承"},
            {"value": "滚柱支承", "label": "滚柱支承"},
        ]
    elif text_type == "bool" or text_type.endswith(" bool"):
        field_type = "select"
        options = [{"value": "true", "label": "是"}, {"value": "false", "label": "否"}]
        default = "true" if bool(default) else "false"
    elif text_type == "str" or text_type.endswith(" str"):
        field_type = "text"

    help_text = f"本项公式直接使用的参数：{_label(key)}。"
    if key == "canal_segments":
        help_text = "按 JSON 数组填写，每段为 {\"slope\":0.001,\"length_m\":100}；渠道分段损失逐段累加。"
    schema: dict[str, Any] = {
        "key": key,
        "label": _label(key),
        "default": default,
        "unit": _unit(key),
        "type": field_type,
        "required": required,
        "help": help_text,
    }
    if options is not None:
        schema["options"] = options
    return schema


def program_input_schema(program_id: str) -> dict[str, Any] | None:
    """Return the canonical editable fields for an adapter-backed program."""
    classes = _FORM_INPUT_CLASSES.get(str(program_id), ())
    additional = _FORM_ADDITIONAL_FIELDS.get(str(program_id), [])
    if not classes and not additional:
        return None
    seen: set[str] = set()
    schema: list[dict[str, Any]] = []
    for input_cls in classes:
        for dataclass_field in fields(input_cls):
            if dataclass_field.name in seen:
                continue
            seen.add(dataclass_field.name)
            field_schema = _form_field_schema(dataclass_field)
            field_schema.update(_FORM_FIELD_OVERRIDES.get((str(program_id), dataclass_field.name), {}))
            schema.append(field_schema)
    schema.extend(additional)
    return {"program_id": str(program_id), "fields": schema}


def _scalar_rows(raw: dict[str, Any]) -> list[dict[str, str]]:
    rows: list[dict[str, str]] = []
    for key, value in raw.items():
        if key == "formula" or isinstance(value, (list, tuple, dict)):
            continue
        rows.append(_row(_label(key), value, _unit(key)))
    return rows


def _generic_payload(program_id: str, title: str, data: Any, calculator: Callable[[Any], Any], source: str) -> dict[str, Any]:
    from .program_registry import get_program
    source = get_program(program_id).source
    result = calculator(data)
    raw = asdict(result) if is_dataclass(result) else dict(result)
    input_rows = _scalar_rows(asdict(data) if is_dataclass(data) else {})
    result_rows = _scalar_rows(raw)
    tables = []
    for key, value in raw.items():
        if isinstance(value, (list, tuple)):
            table_rows = []
            for item in value:
                item_raw = asdict(item) if is_dataclass(item) else item
                if isinstance(item_raw, dict):
                    table_rows.extend(_scalar_rows(item_raw))
            if table_rows:
                tables.append(_table(_label(key), table_rows))
    aliases = {}
    for key in ("peak_flow_m3s", "flow", "flow_m3s", "design_flow", "design_flow_m3_s"):
        if key in raw:
            aliases["peak_flow"] = raw[key]
            break
    return _payload(title, result, input_rows, result_rows, source=source, tables=tables, aliases=aliases)


# @hook UI3-3 remaining-program-adapters 20260804
# Connects remaining registered programs to existing calculation modules without duplicating formulas.
def calculate_irr_02(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("2-02", "灌溉引水流量", _dataclass_input(wi.ContinuousDiversionInput, params), wi.calculate_continuous_diversion, "waterworks_irrigation.py")


def calculate_irr_03(params: dict[str, Any]) -> dict[str, Any]:
    raw = params.get('canal_segments', '[]')
    rows = json.loads(raw) if isinstance(raw, str) else raw
    if not isinstance(rows, list):
        raise HydrologyError('渠道分段损失须为 JSON 数组。')
    segments = tuple(wi.CanalLossSegment(slope=float(row['slope']), length_m=float(row['length_m'])) for row in rows)
    return _generic_payload("2-03", "灌区渠首水位", _dataclass_input(wi.CanalHeadwaterInput, params, {'canal_segments':segments}), wi.calculate_canal_headwater, "waterworks_irrigation.py")


def calculate_irr_05(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("2-05", "提灌站压力管道", _dataclass_input(wi.PressurePipeInput, params), wi.calculate_pressure_pipe, "waterworks_irrigation.py")


def calculate_irr_07(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("2-07", "水泵安装高程", _dataclass_input(wi.PumpInstallationInput, params), wi.calculate_pump_installation_height, "waterworks_irrigation.py")


def calculate_irr_08(params: dict[str, Any]) -> dict[str, Any]:
    power = wi.calculate_pump_total_power(_dataclass_input(wi.PumpPowerInput, params))
    efficiency = wi.calculate_pump_efficiency(_dataclass_input(wi.PumpEfficiencyInput, params))
    speed = wi.calculate_pump_specific_speed(_dataclass_input(wi.PumpSpecificSpeedInput, params))
    result_rows = _scalar_rows(asdict(power)) + _scalar_rows(asdict(efficiency)) + _scalar_rows(asdict(speed))
    input_rows = _scalar_rows({key: params.get(key, _default_for(key)) for key in params})
    return _payload("水泵功率效率及比速", power, input_rows, result_rows, source="waterworks_irrigation.py")


def calculate_irr_09(params: dict[str, Any]) -> dict[str, Any]:
    motor = wi.TransformerMotorInput(
        _f(params, "motor_count", 2),
        _f(params, "motor_rated_power_kw", 45),
        _f(params, "pump_shaft_power_kw", 38),
        _f(params, "motor_efficiency_percent", 88),
        _f(params, "power_factor", 0.85),
        _f(params, "correction_factor", 1.0),
    )
    data = wi.TransformerCapacityInput((motor,), _f(params, "lighting_load_kw", 10), _f(params, "lighting_coincidence_factor", 0.8))
    return _generic_payload("2-09", "泵站主变压器容量", data, wi.calculate_transformer_capacity, "waterworks_irrigation.py")


def calculate_chn_01(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-01", "进水闸引水角度", _dataclass_input(ch.IntakeAngleInput, params), ch.calculate_intake_angle, "channel_hydraulics.py")


def calculate_chn_03(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-03", "临界不冲流速", _dataclass_input(ch.NonScourVelocityInput, params), ch.calculate_non_scour_velocity, "channel_hydraulics.py")


def calculate_chn_04(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-04", "不淤流速", _dataclass_input(ch.NonSiltingVelocityInput, params), ch.calculate_non_silting_velocity, "channel_hydraulics.py")


def calculate_chn_05(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-05", "临界水深", _dataclass_input(ch.CriticalDepthInput, params), ch.calculate_critical_depth, "channel_hydraulics.py")


def calculate_chn_06(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-06", "水力最佳断面", _dataclass_input(ch.BestHydraulicSectionInput, params), ch.calculate_best_hydraulic_section, "channel_hydraulics.py")


def calculate_chn_07(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-07", "最佳实用经济断面", _dataclass_input(ch.PracticalEconomicSectionInput, params), ch.calculate_practical_economic_section, "channel_hydraulics.py")


def calculate_chn_08(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("3-08", "渠道渗漏损失", _dataclass_input(ch.LinedSeepageInput, params), ch.calculate_lined_seepage_loss, "channel_hydraulics.py")


def _parse_json_rows(value: Any, field_label: str) -> list[dict[str, Any]]:
    if not isinstance(value, str) or not value.strip():
        raise HydrologyError(f"{field_label}不能为空，请按本页说明录入 JSON 数组。")
    try:
        rows = json.loads(value)
    except json.JSONDecodeError as exc:
        raise HydrologyError(f"{field_label}不是合法 JSON：{exc.msg}") from exc
    if not isinstance(rows, list) or not rows:
        raise HydrologyError(f"{field_label}必须为至少包含一项的 JSON 数组。")
    if not all(isinstance(row, dict) for row in rows):
        raise HydrologyError(f"{field_label}的每一项必须是对象。")
    return rows


def calculate_chn_18(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "3-18",
        "渠道侧墙稳定与配筋校核",
        _dataclass_input(ch.ChannelSidewallStabilityInput, params),
        ch.calculate_channel_sidewall_stability,
        "channel_hydraulics.py",
    )


def calculate_chn_19(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "3-19",
        "复式断面渠道水力计算",
        _dataclass_input(ch.CompoundChannelInput, params),
        ch.calculate_compound_channel,
        "channel_hydraulics.py",
    )


def calculate_chn_20(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "3-20",
        "U 形渠道水力计算",
        _dataclass_input(ch.UChannelInput, params),
        ch.calculate_u_channel,
        "channel_hydraulics.py",
    )


def calculate_chn_21(params: dict[str, Any]) -> dict[str, Any]:
    rows = _parse_json_rows(params.get("groups_json"), "钢筋分组明细")
    required = {
        "name", "diameter_mm", "unit_weight_kg_per_m", "structure_length_m",
        "protection_total_thickness_m", "spacing_m", "straight_length_m", "bends_count",
    }
    groups: list[ch.RebarGroupInput] = []
    for index, row in enumerate(rows, start=1):
        missing = sorted(required - set(row))
        if missing:
            raise HydrologyError(f"钢筋分组第 {index} 项缺少字段：{', '.join(missing)}")
        groups.append(
            ch.RebarGroupInput(
                name=str(row["name"]),
                diameter_mm=float(row["diameter_mm"]),
                unit_weight_kg_per_m=float(row["unit_weight_kg_per_m"]),
                structure_length_m=float(row["structure_length_m"]),
                protection_total_thickness_m=float(row["protection_total_thickness_m"]),
                spacing_m=float(row["spacing_m"]),
                straight_length_m=float(row["straight_length_m"]),
                bends_count=int(row["bends_count"]),
                segment_count=float(row.get("segment_count", 1.0)),
                layer_count=float(row.get("layer_count", 1.0)),
            )
        )
    data = ch.ReinforcementInput(tuple(groups), _f(params, "concrete_volume_m3", 1.0), _f(params, "loss_rate_percent", 3.0))
    payload = _generic_payload("3-21", "渠道钢筋工程量与含钢量统计", data, ch.calculate_reinforcement, "channel_hydraulics.py")
    payload["input_rows"] = [
        _row("钢筋分组数量", len(groups), "组", "分组明细按配筋图逐项录入"),
        _row("对应混凝土体积", data.concrete_volume_m3, "m³"),
        _row("钢筋损耗率", data.loss_rate_percent, "%"),
    ]
    payload["tables"].insert(0, _table("钢筋分组输入明细", [
        _row(group.name, group.diameter_mm, "mm", f"间距 {group.spacing_m:g} m；直段 {group.straight_length_m:g} m；层数 {group.layer_count:g}")
        for group in groups
    ]))
    payload["calculation_book_text"] = _book(
        "渠道钢筋工程量与含钢量统计计算书",
        payload["input_rows"],
        payload["result_rows"],
        str(payload.get("formula", "")),
        str(payload.get("source", "")),
    )
    return payload


def calculate_chn_22(params: dict[str, Any]) -> dict[str, Any]:
    rows = _parse_json_rows(params.get("points_json"), "纵断面桩号与地面高程")
    points: list[ch.LongitudinalPointInput] = []
    for index, row in enumerate(rows, start=1):
        if "station_no" not in row or "ground_elevation" not in row:
            raise HydrologyError(f"纵断面第 {index} 项必须包含 station_no 和 ground_elevation。")
        distance = float(row.get("distance_from_previous", 0.0))
        if index == 1 and abs(distance) > 1e-9:
            raise HydrologyError("纵断面首点的 distance_from_previous 必须为 0。")
        points.append(ch.LongitudinalPointInput(int(row["station_no"]), float(row["ground_elevation"]), distance))
    data = ch.LongitudinalDesignInput(
        bed_slope=_f(params, "bed_slope", 0.001),
        design_depth=_f(params, "design_depth", 2.0),
        minimum_depth=_f(params, "minimum_depth", 1.2),
        enlarged_depth=_f(params, "enlarged_depth", 2.2),
        freeboard=_f(params, "freeboard", 0.5),
        first_bed_elevation=_f(params, "first_bed_elevation", 1200.0),
        points=tuple(points),
    )
    result = ch.calculate_channel_longitudinal_design(data)
    payload = _generic_payload("3-22", "渠道纵断面设计高程", data, ch.calculate_channel_longitudinal_design, "channel_hydraulics.py")
    payload["input_rows"] = _scalar_rows({
        "bed_slope": data.bed_slope,
        "design_depth": data.design_depth,
        "minimum_depth": data.minimum_depth,
        "enlarged_depth": data.enlarged_depth,
        "freeboard": data.freeboard,
        "first_bed_elevation": data.first_bed_elevation,
        "points": len(points),
    })
    total_excavation = sum(point.excavation_depth for point in result.points)
    total_fill = sum(point.fill_height for point in result.points)
    payload["metrics"] = [
        _row("计算桩号点数量", len(result.points), "个"),
        _row("末点渠底高程", result.points[-1].bed_elevation, "m"),
        _row("末点设计水位", result.points[-1].design_water_level, "m"),
        _row("逐点挖深合计", total_excavation, "m", "为各点挖深算术合计，不替代断面土方体积计算"),
        _row("逐点填高合计", total_fill, "m", "为各点填高算术合计，不替代断面土方体积计算"),
    ]
    payload["result_rows"] = payload["metrics"]
    payload["tables"].insert(0, {
        "title": "纵断面逐桩计算成果",
        "columns": ["桩号", "地面高程(m)", "渠底高程(m)", "最低水位(m)", "设计水位(m)", "加大水位(m)", "渠顶高程(m)", "挖深(m)", "填高(m)"],
        "rows": [[
            point.station_no, point.ground_elevation, point.bed_elevation, point.minimum_water_level,
            point.design_water_level, point.enlarged_water_level, point.top_elevation,
            point.excavation_depth, point.fill_height,
        ] for point in result.points],
    })
    payload["series"] = [
        {
            "title": "渠道纵断面设计线",
            "xKey": "station_no",
            "yKey": "elevation_m",
            "xLabel": "桩号",
            "yLabel": "高程（m）",
            "points": [
                {"station_no": point.station_no, "elevation_m": point.ground_elevation} for point in result.points
            ],
        },
        {
            "title": "渠道纵断面渠底线",
            "xKey": "station_no",
            "yKey": "elevation_m",
            "xLabel": "桩号",
            "yLabel": "高程（m）",
            "points": [
                {"station_no": point.station_no, "elevation_m": point.bed_elevation} for point in result.points
            ],
        },
        {
            "title": "渠道纵断面设计水位线",
            "xKey": "station_no",
            "yKey": "elevation_m",
            "xLabel": "桩号",
            "yLabel": "高程（m）",
            "points": [
                {"station_no": point.station_no, "elevation_m": point.design_water_level} for point in result.points
            ],
        },
    ]
    payload["calculation_book_text"] = _book(
        "渠道纵断面设计高程计算书",
        payload["input_rows"],
        payload["result_rows"],
        str(payload.get("formula", "")),
        str(payload.get("source", "")),
    )
    return payload


def calculate_rsr_02(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("5-02", "水库淤积库容估算", _dataclass_input(rs.ReservoirSedimentationInput, params), rs.calculate_reservoir_sedimentation, "reservoir_storage.py")


def calculate_rsr_03(params: dict[str, Any]) -> dict[str, Any]:
    months = [f"{i}月" for i in range(1, 13)]
    inflows = _float_list(params.get("inflows_m3s"), "8,10,12,18,25,35,30,22,16,12,9,8")
    demands = _float_list(params.get("demands_m3s"), "10,10,12,16,20,22,24,22,18,14,11,10")
    if len(inflows) != 12 or len(demands) != 12:
        raise HydrologyError('年来水及需水序列都应为12个月，不能截断或补齐缺失月份。')
    result = rs.calculate_reservoir_annual_regulation(months, inflows, demands, _f(params, "effective_capacity_m3", 3000000))
    rows = [_row("有效库容", _f(params, "effective_capacity_m3", 3000000), "m³")]
    result_rows = _scalar_rows(asdict(result))
    table_rows = []
    for row in result.rows:
        table_rows.extend(_scalar_rows(asdict(row)))
    columns = ['月份','来水流量(m³/s)','需水流量(m³/s)','余水(m³/s)','亏水(m³/s)','蓄水折合月流量(m³/s)','弃水(m³/s)','说明']
    table = {'title':'逐月运用调节（等月长30天）', 'columns':columns, 'rows':[[r.month,r.inflow,r.demand,r.surplus,r.deficit,r.storage,r.abandoned,r.note] for r in result.rows]}
    series = [{'title':title,'xKey':'month','yKey':'value','xLabel':'月份','yLabel':'流量（m³/s）','points':[{'month':i+1,'value':getattr(r,key)} for i,r in enumerate(result.rows)]} for key,title in [('inflow','月均来水'),('demand','月均需水'),('storage','蓄水折合月流量'),('abandoned','月均弃水')]]
    return _payload("水库年运用调节", result, rows, result_rows, source="等月长法：各月按30天折算，蓄水为等效月流量；初始蓄水取0。", tables=[table], series=series)


def calculate_rsr_05(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("5-05", "土石坝外轮廓尺寸", _dataclass_input(rs.EarthDamProfileInput, params), rs.calculate_earth_dam_profile, "reservoir_storage.py")


def calculate_rsr_06(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("5-06", "大坝护坡厚度", _dataclass_input(rs.DamSlopeProtectionInput, params), rs.calculate_dam_slope_protection, "reservoir_storage.py")


def calculate_rsr_07(params: dict[str, Any]) -> dict[str, Any]:
    data = _dataclass_input(rs.EarthDamProfileInput, params)
    result = rs.calculate_earth_dam_profile(data)
    input_rows = _scalar_rows(asdict(data))
    result_rows = [_row("帷幕深度", result.curtain_depth_m, "m", "按土石坝外轮廓尺寸计算结果提取")]
    return _payload("坝基悬挂帷幕深度", result, input_rows, result_rows, source="reservoir_storage.py")


def calculate_rsr_09(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("5-09", "平面闸门启闭力", _dataclass_input(rs.PlaneGateForceInput, params), rs.calculate_plane_gate_force, "reservoir_storage.py")


def calculate_hyd_02(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("6-02", "装机容量计算与选择", _dataclass_input(hp.InstalledCapacityInput, params), hp.calculate_installed_capacity, "hydropower.py")


def calculate_hyd_04(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("6-04", "管道进口最小淹没水深", _dataclass_input(hp.PipeSubmergenceInput, params), hp.calculate_pipe_minimum_submergence, "hydropower.py")


def calculate_hyd_05(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("6-05", "通气管面积与直径", _dataclass_input(hp.VentPipeInput, params), hp.calculate_vent_pipe, "hydropower.py")


def calculate_hyd_07(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("6-07", "压力管道水头损失", _dataclass_input(hp.PipeHeadLossInput, params), hp.calculate_pipe_head_loss, "hydropower.py")


def calculate_hyd_08(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "6-08",
        "压力钢管抗外压稳定",
        _dataclass_input(hp.ExternalPressureInput, params),
        hp.calculate_external_pressure,
        "hydropower.py",
    )


def calculate_hyd_09(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "6-09",
        "输电导线经济截面与载流量校核",
        _dataclass_input(hp.TransmissionConductorInput, params),
        hp.calculate_transmission_conductor,
        "hydropower.py",
    )


def calculate_hyd_10(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "6-10",
        "主变压器容量校核",
        _dataclass_input(hp.TransformerCapacityInput, params),
        hp.calculate_transformer_capacity,
        "hydropower.py",
    )


def calculate_hyd_11(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "6-11",
        "电力负荷预测与供发电最大负荷",
        _dataclass_input(hp.LoadForecastInput, params),
        hp.calculate_load_forecast,
        "hydropower.py",
    )


def calculate_hst_04(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("7-04", "消力池和海漫", _dataclass_input(hs.StillingBasinInput, params), hs.calculate_stilling_basin, "hydraulic_structures_book.py")


def calculate_hst_05(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("7-05", "陡坡水力", _dataclass_input(hs.SteepSlopeInput, params), hs.calculate_steep_slope, "hydraulic_structures_book.py")


def calculate_hst_09(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload("7-09", "桥孔水力", _dataclass_input(hs.BridgeHydraulicInput, params), hs.calculate_bridge_hydraulic, "hydraulic_structures_book.py")


def calculate_hst_06(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "7-06",
        "跌水水力与消能尺寸",
        _dataclass_input(hs.VerticalDropInput, params),
        hs.calculate_vertical_drop,
        "hydraulic_structures_book.py",
    )


def calculate_hst_10(params: dict[str, Any]) -> dict[str, Any]:
    normalized = {**params}
    if normalized.get("mode") in (None, ""):
        normalized["mode"] = "无压城门洞形"
    return _generic_payload(
        "7-10",
        "隧洞过流断面水力",
        _dataclass_input(hs.TunnelHydraulicInput, normalized),
        hs.calculate_tunnel_hydraulic,
        "hydraulic_structures_book.py",
    )


def calculate_hst_11(params: dict[str, Any]) -> dict[str, Any]:
    return _generic_payload(
        "7-11",
        "涵洞泄流断面水力",
        _dataclass_input(hs.CulvertHydraulicInput, params),
        hs.calculate_culvert_hydraulic,
        "hydraulic_structures_book.py",
    )


def calculate_hst_12(params: dict[str, Any]) -> dict[str, Any]:
    normalized = {**params}
    if normalized.get("mode") in (None, ""):
        normalized["mode"] = "锐角分水"
    return _generic_payload(
        "7-12",
        "侧堰分水与泄流能力",
        _dataclass_input(hs.SideWeirInput, normalized),
        hs.calculate_side_weir,
        "hydraulic_structures_book.py",
    )
